package coredb;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.NoResultException;
import javax.persistence.Persistence;
import javax.persistence.PersistenceException;
import javax.persistence.Query;
import javax.persistence.RollbackException;
import java.util.ArrayList;


/** Implements functionalities for operations in a bank by bank personnel.
 * Includes transactions such as deposit and withdraw, report generation for
 * daily account summary and summary for a date range, getting balance of an 
 * account.
 * @author Abhishek Anand
 */
public class Queries implements Functionalities {
    
    static final Logger logger = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME);
    /**
     *
     * @param accNo Account number of a customer 
     * @return number of times withdraw operation was made today for given 
     * account.
     */
    // TODO: check and can be better implemented, this will not scale
    @Override
    public final int withdrawlCount(long accNo) throws ParseException {
        EntityManager e = getEntityManager();
        Query q = e.createNamedQuery("Transaction.findByAccno");
        Query q2 = e.createNamedQuery("Account.findByAccno");
        q2.setParameter("accno",accNo);
        Account a = (Account)q2.getSingleResult();
        q.setParameter("accno", a);
        List<Transaction> lt = q.getResultList();
        int tcount = 0;
        SimpleDateFormat d = new SimpleDateFormat("yyyy-MM-dd");
        String today = d.format(new Date());
        for (Transaction t: lt) {
            if (d.format(t.getTimeoftxn()).equals(today) && t.getTypeoftxn().equals("W")) {
                tcount++;
            }
        }
        return tcount;
    }

    /**
     *
     * @param accNo Account number of customer
     * @param date  A date specified in YYYY-MM-DD format string for which
     * query is to be done
     * @return total amount withdrawn on given date
     */
    // TODO: check and can be better implemented, this will not scale
    @Override
    public double withdrawlAmount(long accNo) throws ParseException {
        EntityManager e = getEntityManager();
        Query q = e.createNamedQuery("Transaction.findByAccno");
        Query q2 = e.createNamedQuery("Account.findByAccno");
        q2.setParameter("accno",accNo);
        Account a = (Account)q2.getSingleResult();
        q.setParameter("accno", a);
        List<Transaction> lt = q.getResultList();
        SimpleDateFormat d = new SimpleDateFormat("yyyy-MM-dd");
        String today = d.format(new Date());
        double tamount = 0.0;
        for (Transaction t: lt) {
            if (d.format(t.getTimeoftxn()).equals(today) && t.getTypeoftxn().equals("W")) {
                tamount += t.getAmount();
            }
        }
        return tamount;
        
    }
    
    /**
     *
     * @param a Account on which transaction is being done
     * @param amount Amount for the transaction
     * @param typeOfTxn A string denoting which type of transaction
     * Deposit or Withdraw ('D' or 'W') is made.
     */
    @Override
    public boolean recordTransaction(Account a, double amount, String typeOfTxn) {
        Transaction t = new Transaction();
        boolean state = false;
        EntityManager e = getEntityManager();
        Random r = new Random();
        e.getTransaction().begin();
        try {
            t.setTxnid(String.valueOf(Math.abs(r.nextInt())));
            t.setAccno(a);
            t.setAmount(amount);
            t.setTimeoftxn(new Date());
            t.setTypeoftxn(typeOfTxn);
            e.persist(t);
            e.getTransaction().commit();
            state = true;
        } catch (RollbackException rb) {
            e.getTransaction().rollback();
        } catch (PersistenceException p) {
            logger.log(Level.SEVERE, "Database connection problem {0}", a.toString());
            state = false;
        }
        e.close();
        return state;
    }

    /**
     *
     * @param name Name of customer
     * @param dob Date of birth of customer in YYYY-MM-DD String format
     * @param pan Permanent Account Number of the customer. PAN has to be
     * exactly 10 characters long of which first 5 are alphabets, next 4 are
     * digits and last one is alphabet.
     * @param bal Initial or opening balance for the new account.
     * @return Generated account number of the user if account is created or -1
     * if could not be created.
     */
    @Override
    public long createAccount(String name, String dob, String pan, double bal) throws CustomerExistsException {
        Customer c = createCustomer(name, dob, pan);
        EntityManager e = getEntityManager();
        Random r = new Random();
        int accNo = Math.abs(r.nextInt());
        e.getTransaction().begin();
        Account a;
        if (c != null) {
            try {
                a = new Account();
                // If account number has to be random and not auto-incr uncomment
                //a.setAccno(accNo);
                a.setUserid(c);
                a.setBalance(accNo);
                a.setBalance(bal);
                e.persist(a);
                e.getTransaction().commit();
            } catch (RollbackException rb) {
                e.getTransaction().rollback();
                return ACCOUNT_NOT_CREATED;
            } catch (PersistenceException p) {
                logger.log(Level.SEVERE, "Database connection problem {0}", c.toString());
                return ACCOUNT_NOT_CREATED;
            }
            e.close();
            return a.getAccno();
        } else {
            return ACCOUNT_NOT_CREATED;
        }
       
    }
    
    /**
     *
     * @param name Name of customer
     * @param dob Date of birth of customer in YYYY-MM-DD format string.
     * @param pan Permanent Account Number of the customer. PAN has to be
     * exactly 10 characters long of which first 5 are alphabets, next 4 are
     * digits and last one is alphabet.
     * @return Object of type Customer with given data
     */
    @Override
    public Customer createCustomer(String name, String dob, String pan) throws CustomerExistsException {
        EntityManager e = getEntityManager();
        Random r = new Random();
        int uid = Math.abs(r.nextInt());
        Customer c = null;
        Query q= e.createNamedQuery("Customer.findByPan");
        q.setParameter("pan", pan);
        try {
            Customer already = (Customer)q.getSingleResult();
            throw new CustomerExistsException();
        } catch(NoResultException nre) {
        e.getTransaction().begin();
        c = new Customer();
        // TODO: check caps or small YYYY
        SimpleDateFormat d = new SimpleDateFormat("yyyy-MM-dd");
        Date fDob;
        try {
            fDob = d.parse(dob);
            c.setName(name);
            c.setPan(pan);
            c.setDob(fDob);
            c.setUserId(uid);
            e.persist(c);
            e.getTransaction().commit();
            e.close();
        } catch (ParseException p) {
            e.getTransaction().rollback();
            return null;
        } catch(PersistenceException pe) {
            logger.log(Level.SEVERE, "Database connection problem {0}", c.toString());
            return null;
        }
        } finally {
            return c;
        }
    }

    /**
     *
     * @return List of all objects of type Account stored in database
     */
    @Override
    public List<Account> getAllAccounts() {
        EntityManager e = getEntityManager();
        Query q = e.createNamedQuery("Account.findAll");
        return q.getResultList();
    }

    /**
     *
     * @param accNo A valid account number
     * @return current balance if account number exists otherwise return -1
     */
    @Override
    public double getBal(long accNo) {
        EntityManager e = getEntityManager();
        Query q = e.createNamedQuery("Account.findByAccno");
        q.setParameter("accno", accNo);
        Account a = null;
        try {
            a = (Account) q.getSingleResult();
        } catch (Exception ex) {
            //TODO remove -1 !!!!!!!!!
            return ACCOUNT_NOT_FOUND;
        }
        return a.getBalance();
    }

    /** This deposits is to be used for transactions with value less than
     * <code>PAN_REQUIRED_LT</code>
     *
     * @param accNo A valid account number
     * @param amount Amount to be deposited in the account
     * @return True if transaction is successfully done else return false
     */
    @Override
    public boolean deposit(long accNo, double amount) throws NoResultException {
        EntityManager e = getEntityManager();
        Query q = e.createNamedQuery("Account.findByAccno");
        q.setParameter("accno", accNo);
        Account a = (Account) q.getSingleResult();
        double curBalance = a.getBalance();
        
        boolean state;
        e.getTransaction().begin();
        try {
            curBalance += amount;
            Query q2 = e.createNamedQuery("Account.updateBalance");
            q2.setParameter("balance", curBalance);
            q2.setParameter("accno", accNo);
            int s = q2.executeUpdate();
            //TODO: check s and then commit
            e.getTransaction().commit();
            e.close();
            state = true;
            recordTransaction(a, amount, "D");
        } catch (RollbackException r) {
            e.getTransaction().rollback();
            state = false;
        }
        return state;
    }

    /** deposit for amount greater than <code>PAN_REQUIRED_LT</code>
     *
     * @param accNo A valid account number
     * @param amount Amount to be deposited
     * @param pan PAN for the customer
     * @return true if deposit is successful else return false
     */
    @Override
    public boolean deposit(long accNo, double amount, String pan) throws NoResultException {
        boolean state = false;
        EntityManager e = getEntityManager();
        Query q = e.createNamedQuery("Customer.findByPan");
        q.setParameter("pan", pan);
        Customer c = null;
        try {
            c = (Customer) q.getSingleResult();
        } catch(NoResultException nr) {
            // TODO PAN does not exist !!!
            return false;
        }
        if (c == null) {
            // customer does not exist
            return false;
        } else {
            Query q2 = e.createNamedQuery("Account.findByAccno");
            q2.setParameter("accno", accNo);
            Account a;
            try {
                a = (Account) q2.getSingleResult();
            } catch(NoResultException nre) {
                return false;
            }
            if (a.getUserid().getUserId() == c.getUserId()) {
                state = deposit(accNo, amount);
            }
        }
        return state;
    }
    
    /**
     *
     * @param accNo A valid account number
     * @param amount Amount to be deposited
     * @return true if withdraw is successful else return false
     */
    @Override
    public boolean withdraw(long accNo, double amount) throws NoResultException {
        boolean state = false;
        if (amount <= 0) {
            return state;
        }
        EntityManager e = getEntityManager();
        Query q = e.createNamedQuery("Account.findByAccno");
        q.setParameter("accno", accNo);
        Account a = (Account) q.getSingleResult();
        System.out.println(a.getBalance());
        try {
            double todayAmt = withdrawlAmount(accNo);
            int todayLt = withdrawlCount(accNo);
            if (todayAmt + amount > MAX_WITHDRAWL_AMOUNT) {
                return false;
            }
            if (todayLt >= MAX_WITHDRAWL_COUNT || todayAmt >= MAX_WITHDRAWL_AMOUNT || a.getBalance() < amount) {
                return false;
            } else {
                e.getTransaction().begin();
                double curBalance = a.getBalance();
                try {
                    curBalance -= amount;
                    Query q2 = e.createNamedQuery("Account.updateBalance");
                    q2.setParameter("balance", curBalance);
                    q2.setParameter("accno", accNo);
                    int s = q2.executeUpdate();
                    e.getTransaction().commit();
                    state = true;
                    recordTransaction(a, amount, "W");
                    //TODO: check s and then commit
                } catch (RollbackException r) {
                    e.getTransaction().rollback();
                    return false;
                }

                e.close();
            }
        } catch (ParseException p) {
            return state;
        }
        return state;

 }

    /**
     *
     * @return List of transactions for today
     */
    @Override
    public List<Transaction> getDailyReport() {
        EntityManager e = getEntityManager();
        Query q = e.createNamedQuery("Transaction.findAll");
        List<Transaction> l = q.getResultList();
        SimpleDateFormat d = new SimpleDateFormat("yyyy-MM-dd");
        String today = d.format(new Date());
        List<Transaction> result = new ArrayList<Transaction>();
        for (Transaction t: l) {
            String txndate = d.format(t.getTimeoftxn());
            if (txndate.equals(today)) {
                result.add(t);
            }
        }
        return result;
    }

    /**
     *
     * @param accNo A valid account number
     * @param start a string of format YYYY-MM-DD
     * @param end a string of format YYYY-MM-DD
     * @return List of transactions for the particular account for the given time period
     */
    @Override
    public List<Transaction> getTxnReport(long accNo, String start, String end) throws ParseException {
        EntityManager e = getEntityManager();
        Query q = e.createNamedQuery("Transaction.findByAccno");
        Query q2 = e.createNamedQuery("Account.findByAccno");
        q2.setParameter("accno",accNo);
        Account a = (Account)q2.getSingleResult();
        q.setParameter("accno", a);
        List<Transaction> l = q.getResultList();
        SimpleDateFormat d = new SimpleDateFormat("yyyy-MM-dd");
        Date st =d.parse(start);
        Date ed =d.parse(end);
        List<Transaction> result = new ArrayList<Transaction>();
        
        for (Transaction t: l) {
            Date tdate = d.parse(d.format(t.getTimeoftxn()));
            if ((tdate.after(st) && tdate.before(ed)) ||tdate.equals(st) || tdate.equals(ed)) {
                result.add(t);
            }
        }
        return result;
    }
       

    private static EntityManager getEntityManager() {
        EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("WebApplication1PU");
        return entityManagerFactory.createEntityManager();
    }
}
